<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>助眠放松</title>

  <!-- PWA 必需 -->
  <meta name="theme-color" content="#4f46e5">
  <link rel="manifest" href="manifest.json">

  <!-- iOS Safari 支持 -->
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="default">
  <meta name="apple-mobile-web-app-title" content="助眠放松">
  <link rel="apple-touch-icon" href="icon-192.png">

  <!-- 通用 favicon -->
  <link rel="icon" type="image/png" sizes="192x192" href="icon-192.png">
  <style>
    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
      max-width: 520px;
      margin: 40px auto;
      padding: 20px;
      background: #f8fafc;
      color: #1e293b;
      line-height: 1.6;
    }
    h1 {
      text-align: center;
      font-size: 1.6em;
      margin-bottom: 25px;
      color: #4f46e5;
    }
    .control-group {
      margin: 20px 0;
    }
    label {
      display: block;
      margin-bottom: 8px;
      font-weight: 600;
      color: #334155;
    }
    select, input[type="range"] {
      width: 100%;
      padding: 10px;
      border: 1px solid #cbd5e1;
      border-radius: 8px;
      font-size: 16px;
      background: white;
    }
    select:disabled, input[type="range"]:disabled {
      background: #f1f5f9;
      color: #94a3b8;
      cursor: not-allowed;
    }
    .volume-display {
      text-align: right;
      font-size: 0.9em;
      color: #64748b;
      margin-top: 4px;
    }
    button {
      width: 100%;
      padding: 14px;
      font-size: 1.1em;
      border: none;
      border-radius: 10px;
      cursor: pointer;
      margin: 12px 0;
      font-weight: 600;
      transition: all 0.2s;
    }
    #startBtn {
      background: #4f46e5;
      color: white;
    }
    #startBtn:hover:not(:disabled) {
      background: #4338ca;
    }
    #stopBtn {
      background: #e2e8f0;
      color: #475569;
    }
    #stopBtn:hover:not(:disabled) {
      background: #cbd5e1;
    }
    #stopBtn:disabled {
      display: none;
    }
    .timer-display {
      text-align: center;
      font-size: 1.2em;
      margin: 15px 0;
      color: #4f46e5;
      font-weight: 600;
    }
    .note-box {
      background: #f1f5f9;
      border-left: 4px solid #4f46e5;
      padding: 16px;
      border-radius: 0 8px 8px 0;
      margin-top: 25px;
      font-size: 0.95em;
      color: #334155;
    }
    .note-box h3 {
      margin-top: 0;
      color: #4f46e5;
      font-size: 1.1em;
    }
    .status {
      text-align: center;
      margin: 12px 0;
      font-size: 0.95em;
      color: #64748b;
    }
    .highlight {
      background: #dbeafe;
      padding: 2px 6px;
      border-radius: 4px;
      font-weight: 600;
    }
    .preset-info {
      font-size: 0.88em;
      color: #64748b;
      margin-top: 6px;
      padding: 8px;
      background: #f8fafc;
      border-radius: 6px;
    }
  </style>
</head>
<body>
  <h1>🧠 粉红噪音 · 场景化助眠 & 专注</h1>

  <div class="control-group">
    <label for="preset">使用场景</label>
    <select id="preset">
      <option value="sleep">🌙 睡前 15 分钟（θ 波助眠）</option>
      <option value="focus">💼 工作专注（仅粉红噪音）</option>
      <option value="meditation">🧘 冥想放松（α 波舒缓）</option>
      <option value="custom">⚙️ 自定义组合</option>
    </select>
    <div class="preset-info" id="presetInfo">
      推荐音量：40% | 双耳节拍：θ 波 (5 Hz)
    </div>
  </div>

  <div class="control-group">
    <label for="mode">双耳节拍模式</label>
    <select id="mode" disabled>
      <option value="theta">θ 波 · 深度入眠 (5 Hz)</option>
      <option value="alpha">α 波 · 放松冥想 (8 Hz)</option>
      <option value="none">仅粉红噪音</option>
    </select>
  </div>

  <div class="control-group">
    <label for="duration">播放时长</label>
    <select id="duration">
      <option value="900">15 分钟</option>
      <option value="1800">30 分钟</option>
      <option value="3600">60 分钟</option>
      <option value="0">无限循环</option>
    </select>
  </div>

  <div class="control-group">
    <label for="volume">总音量</label>
    <input type="range" id="volume" min="0" max="1" step="0.01" value="0.4">
    <div class="volume-display" id="volumeDisplay">40%</div>
  </div>

  <div class="status" id="statusIndicator">准备就绪</div>

  <button id="startBtn">▶ 开始播放（请佩戴耳机）</button>
  <button id="stopBtn" disabled>⏹ 停止播放</button>

  <div class="timer-display" id="timerDisplay"></div>

  <div class="note-box">
    <h3>💡 使用说明</h3>
    <ul>
      <li>选择预设场景后，系统自动配置最佳参数（音量可微调）</li>
      <li>“自定义”模式下可自由组合双耳节拍与音量</li>
      <li>必须<strong>佩戴耳机</strong>才能体验双耳节拍效果</li>
      <li>首次无声？请允许浏览器声音权限（地址栏左侧图标）</li>
    </ul>
  </div>

  <script>
    const PRESETS = {
      sleep: { mode: 'theta', volume: 0.40, info: '推荐音量：40% | 双耳节拍：θ 波 (5 Hz)' },
      focus: { mode: 'none',  volume: 0.30, info: '推荐音量：30% | 无双耳节拍' },
      meditation: { mode: 'alpha', volume: 0.35, info: '推荐音量：35% | 双耳节拍：α 波 (8 Hz)' }
    };

    let audioContext = null;
    let masterGain = null;
    let sources = [];
    let isPlaying = false;
    let timerInterval = null;
    let totalSeconds = 0;

    const presetSelect = document.getElementById('preset');
    const modeSelect = document.getElementById('mode');
    const durationSelect = document.getElementById('duration');
    const volumeSlider = document.getElementById('volume');
    const volumeDisplay = document.getElementById('volumeDisplay');
    const presetInfo = document.getElementById('presetInfo');
    const startBtn = document.getElementById('startBtn');
    const stopBtn = document.getElementById('stopBtn');
    const timerDisplay = document.getElementById('timerDisplay');
    const statusIndicator = document.getElementById('statusIndicator');

    presetSelect.addEventListener('change', () => {
      const preset = presetSelect.value;
      if (preset === 'custom') {
        modeSelect.disabled = false;
        presetInfo.textContent = '自由组合双耳节拍与音量';
      } else {
        const config = PRESETS[preset];
        modeSelect.value = config.mode;
        volumeSlider.value = config.volume;
        volumeDisplay.textContent = Math.round(config.volume * 100) + '%';
        modeSelect.disabled = true;
        presetInfo.textContent = config.info;
      }
    });

    volumeSlider.addEventListener('input', () => {
      const vol = parseFloat(volumeSlider.value);
      volumeDisplay.textContent = Math.round(vol * 100) + '%';
      if (masterGain) masterGain.gain.value = vol;
    });

    function createPinkNoiseSource(context) {
      const duration = 2;
      const sampleRate = context.sampleRate;
      const bufferSize = Math.floor(duration * sampleRate);
      const buffer = context.createBuffer(2, bufferSize, sampleRate);
      const left = buffer.getChannelData(0);
      const right = buffer.getChannelData(1);

      let b0L = 0, b1L = 0, b2L = 0, b3L = 0, b4L = 0, b5L = 0, b6L = 0;
      let b0R = 0, b1R = 0, b2R = 0, b3R = 0, b4R = 0, b5R = 0, b6R = 0;

      for (let i = 0; i < bufferSize; i++) {
        const whiteL = Math.random() * 2 - 1;
        b0L = 0.99886 * b0L + whiteL * 0.0555179;
        b1L = 0.99332 * b1L + whiteL * 0.0750759;
        b2L = 0.96900 * b2L + whiteL * 0.1538520;
        b3L = 0.86650 * b3L + whiteL * 0.3104856;
        b4L = 0.55000 * b4L + whiteL * 0.5329522;
        b5L = -0.7616 * b5L - whiteL * 0.0168980;
        left[i] = (b0L + b1L + b2L + b3L + b4L + b5L + b6L + whiteL * 0.5362) * 0.11;
        b6L = whiteL * 0.115926;

        const whiteR = Math.random() * 2 - 1;
        b0R = 0.99886 * b0R + whiteR * 0.0555179;
        b1R = 0.99332 * b1R + whiteR * 0.0750759;
        b2R = 0.96900 * b2R + whiteR * 0.1538520;
        b3R = 0.86650 * b3R + whiteR * 0.3104856;
        b4R = 0.55000 * b4R + whiteR * 0.5329522;
        b5R = -0.7616 * b5R - whiteR * 0.0168980;
        right[i] = (b0R + b1R + b2R + b3R + b4R + b5R + b6R + whiteR * 0.5362) * 0.11;
        b6R = whiteR * 0.115926;
      }

      const fadeSamples = Math.min(100, Math.floor(sampleRate * 0.005));
      for (let i = 0; i < fadeSamples; i++) {
        const g = i / fadeSamples;
        left[i] *= g;
        right[i] *= g;
        left[bufferSize - 1 - i] *= g;
        right[bufferSize - 1 - i] *= g;
      }

      const source = context.createBufferSource();
      source.buffer = buffer;
      source.loop = true;
      return source;
    }

    async function startAudio() {
      if (isPlaying) return;

      const mode = modeSelect.value;
      const durationSec = parseInt(durationSelect.value);
      const volume = parseFloat(volumeSlider.value);

      try {
        statusIndicator.textContent = '初始化音频...';

        if (!audioContext) {
          audioContext = new (window.AudioContext || window.webkitAudioContext)();
        }
        if (audioContext.state === 'suspended') {
          await audioContext.resume();
        }

        masterGain = audioContext.createGain();
        masterGain.gain.value = 0;
        masterGain.connect(audioContext.destination);

        sources = [];

        const pinkNoise = createPinkNoiseSource(audioContext);
        pinkNoise.connect(masterGain);
        pinkNoise.start();
        sources.push(pinkNoise);

        if (mode !== 'none') {
          const carrier = 200;
          const beatFreq = mode === 'theta' ? 5 : 8;
          const leftOsc = audioContext.createOscillator();
          const rightOsc = audioContext.createOscillator();
          leftOsc.frequency.value = carrier + beatFreq / 2;
          rightOsc.frequency.value = carrier - beatFreq / 2;
          const merger = audioContext.createChannelMerger(2);
          leftOsc.connect(merger, 0, 0);
          rightOsc.connect(merger, 0, 1);
          merger.connect(masterGain);
          leftOsc.start();
          rightOsc.start();
          sources.push(leftOsc, rightOsc);
        }

        const schumann = audioContext.createOscillator();
        const schumannGain = audioContext.createGain();
        schumann.type = 'sine';
        schumann.frequency.value = 7.83;
        schumannGain.gain.value = 0.0005;
        schumann.connect(schumannGain);
        schumannGain.connect(masterGain);
        schumann.start();
        sources.push(schumann);

        masterGain.gain.setValueAtTime(0, audioContext.currentTime);
        masterGain.gain.linearRampToValueAtTime(volume, audioContext.currentTime + 5);

        if (durationSec > 0 && durationSec > 10) {
          setTimeout(() => {
            if (masterGain && audioContext.state !== 'closed') {
              masterGain.gain.cancelScheduledValues(audioContext.currentTime);
              masterGain.gain.setValueAtTime(volume, audioContext.currentTime);
              masterGain.gain.linearRampToValueAtTime(0, audioContext.currentTime + 10);
            }
          }, (durationSec - 10) * 1000);
        }

        if (durationSec > 0) {
          totalSeconds = durationSec;
          updateTimer();
          timerInterval = setInterval(updateTimer, 1000);
          setTimeout(stopAudio, durationSec * 1000);
        } else {
          timerDisplay.textContent = '⏱️ 无限循环';
        }

        isPlaying = true;
        startBtn.disabled = true;
        stopBtn.disabled = false;
        statusIndicator.textContent = '🎧 正在播放中...';

      } catch (err) {
        console.error('启动失败:', err);
        statusIndicator.textContent = '❌ 启动失败，请允许声音权限';
        cleanup();
      }
    }

    function updateTimer() {
      if (totalSeconds <= 0) {
        clearInterval(timerInterval);
        timerDisplay.textContent = '';
        return;
      }
      const mins = Math.floor(totalSeconds / 60);
      const secs = totalSeconds % 60;
      timerDisplay.textContent = `剩余时间：${mins}:${secs.toString().padStart(2, '0')}`;
      totalSeconds--;
    }

    function cleanup() {
      sources.forEach(src => {
        try { src.stop(); } catch (e) {}
      });
      sources = [];
      if (masterGain) {
        masterGain.disconnect();
        masterGain = null;
      }
      if (audioContext && audioContext.state !== 'closed') {
        audioContext.close();
        audioContext = null;
      }
    }

    function stopAudio() {
      if (!isPlaying) return;
      clearInterval(timerInterval);
      cleanup();
      isPlaying = false;
      startBtn.disabled = false;
      stopBtn.disabled = true;
      timerDisplay.textContent = '';
      statusIndicator.textContent = '已停止';
    }

    presetSelect.dispatchEvent(new Event('change'));

    startBtn.addEventListener('click', startAudio);
    stopBtn.addEventListener('click', stopAudio);
    window.addEventListener('beforeunload', stopAudio);

    // 注册 Service Worker
    if ('serviceWorker' in navigator) {
      window.addEventListener('load', () => {
        navigator.serviceWorker.register('./sw.js')
          .then(reg => console.log('SW registered:', reg))
          .catch(err => console.log('SW failed:', err));
      });
    }
  </script>
</body>
</html>